package Meter::Action;

#===============================================================================
#     REVISION:  $Id: Action.pm 107 2011-07-21 11:47:32Z xdr.box@gmail.com $
#  DESCRIPTION:  Base class for Meter::Action::*
#          TODO  validate all CGI params before passing them into action module
#===============================================================================

use strict;
use warnings;

use Readonly;
our $VERSION = qw($Revision: 107 $) [1]; ## no critic (RequireConstantVersion)

use English qw( -no_match_vars );
use Template;
use DateTime;
use Carp;

#use Smart::Comments;

use Meter::Utils qw(
    dump_var
    get_webapp_url
    check_session_id
    get_action_name_by_action_module
);

use Meter::Log;
use Meter::LogParams;

use Meter::Sessions;

sub new {
    my $class  = shift;
    my $cgi    = shift;
    my $config = shift;

    my $tt = Template->new(
        INCLUDE_PATH => "$ENV{'METER_ROOT'}/www/templates",
        WRAPPER      => 'html_wrapper.tt2',
        EVAL_PERL    => 1,
    );

    my $self = {
        cgi     => $cgi,
        config  => $config,
        params  => { $cgi->Vars() },
        tt      => $tt,
        headers => {},
    };

    # action parameter can be empty
    $self->{'params'}{'action'} = get_action_name_by_action_module($class);

    my $logger = get_logger();

    $logger->debug(
        "$class CGI params: " . dump_var( $self->{'params'}, 'PARAMS' ) );

    return bless $self, $class;
}

sub set_header {
    my $self  = shift;
    my $name  = shift;
    my $value = shift;

    $self->{'headers'}{$name} = $value;

    return;
}

sub generate_headers : Log( params => 0 ) {
    my $self = shift;

    return $self->{'cgi'}->header( %{ $self->{'headers'} } );
}

sub process {
    my $self = shift;

    $self->set_header( -charset => $self->{'config'}{'charset'} );

    $self->{'session'} = $self->get_session();

    if ( $self->{'session'} ) {
        $self->{'user'} = $self->{'session'}->user();

        $self->{'session'}->last_activity_datetime( $self->now() );
        $self->{'session'}->update();
    }

    my $content = q{};

    if ( $self->is_auth_requied() and not $self->is_user_logged_in() ) {
        $content = $self->redirect_to_login_page();
    }
    else {
        $content = $self->generate_page_content();
    }

    my $headers = $self->generate_headers();

    print $headers;
    print $content;

    return;
}

sub process_template {
    my $self     = shift;
    my $template = shift;
    my $vars     = shift;

    my $output   = q{};
    my $username = $self->get_username();

    $self->{'tt'}->process(
        $template,
        {   %{ $self->{'config'} },
            (   $username
                ? ( username => $username )
                : ()
            ),
            %{$vars},
        },
        \$output
        )
        or confess(
        "Cannot process template '$template': " . $self->{'tt'}->error() );

    return $output;
}

sub get_session {
    my $self = shift;

    my $logger     = get_logger();
    my $session_id = $self->{'cgi'}->cookie('SessionID');

    return if !$session_id;

    if ( !check_session_id($session_id) ) {
        $logger->warn("Invalid session ID: '$session_id'");
        return;
    }

    my $session = Meter::Sessions->retrieve($session_id);

    if ( !$session ) {
        $logger->debug("Session '$session_id' not found");
        return;
    }

    if ( $self->is_session_expired($session) ) {
        $logger->debug("Session '$session_id' has expired");
        return;
    }

    return $session;
}

sub is_user_logged_in {
    my $self = shift;

    return $self->{'user'} && $self->{'session'};
}

sub is_session_expired {
    my $self    = shift;
    my $session = shift;

    my $now             = $self->now();
    my $expiration_date = $session->last_activity_datetime()
        ->add( minutes => $self->{'config'}{'session_life_time_minutes'} );

    return $now > $expiration_date;
}

sub redirect_to_login_page {
    my $self = shift;

    my $logger       = get_logger();
    my $action_class = ref $self;

    $logger->warn(
        "Attempt to process $action_class action without being logged in");

    return $self->redirect_error(
        'Необходимо войти в систему', 'login' );
}

sub now {
    my $self = shift;

    return DateTime->now( time_zone => $self->{'config'}{'time_zone'} );
}

# child classes can override this
sub is_auth_requied {
    my $self = shift;

    return 1;
}

sub redirect_ok : Log( params_as_hash => 0, result => 0 ) {
    my $self    = shift;
    my $message = shift;
    my $action  = shift;

    return $self->_show_result_and_redirect( $message, $action, 'ok' );
}

sub redirect_error : Log( params_as_hash => 0, result => 0 ) {
    my $self    = shift;
    my $message = shift;
    my $action  = shift;
    my $why     = shift;

    my $user_friendly_error
        = $why ? $self->_get_user_friendly_error($why) : undef;

    if ($user_friendly_error) {
        $message = "$message: $user_friendly_error";
    }

    return $self->_show_result_and_redirect( $message, $action, 'error' );
}

sub _show_result_and_redirect {
    my $self        = shift;
    my $message     = shift;
    my $action      = shift;
    my $ok_or_error = shift;

    return $self->process_template(
        "result_${ok_or_error}.tt2",
        {   message         => $message,
            redirect_to_url => get_webapp_url($action),
            redirect_delay =>
                $self->{'config'}{"redirect_${ok_or_error}_delay_seconds"},
        }
    );
}

sub _get_user_friendly_error {
    my $self = shift;
    my $why  = shift;

    my $error_regexes_list = $self->get_error_regexes_list();

    foreach my $error ( @{$error_regexes_list} ) {
        if ( $why =~ $error->{'regex'} ) {
            return $error->{'message'};
        }
    }

    return;
}

# child classes can redefine this method if needed
sub get_error_regexes_list {
    my $self = shift;

    return [];
}

sub get_username {
    my $self = shift;

    if ( ref $self->{'user'} eq 'Meter::Users' ) {
        return $self->{'user'}->username();
    }
    else {
        return;
    }
}

# child action classes usually redefine page generation method
sub generate_page_content {
    my $self = shift;

    my $logger = get_logger();
    my $action = $self->{'params'}{'action'};

    $logger->info("Showing simple page for action '$action'");

    return $self->process_template( "${action}.tt2", $self->{'params'} );
}

1;
